package com.example.system.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.common.constant.RedisConstant;
import com.example.common.redis.RedisUtils;
import com.example.common.response.R;
import com.example.common.utils.StringUtils;
import com.example.system.domain.bo.MenuBo;
import com.example.system.domain.bo.PageBo;
import com.example.system.domain.entity.Menu;
import com.example.system.domain.entity.Role;
import com.example.system.domain.entity.RoleMenu;
import com.example.system.domain.vo.PageVo;
import com.example.system.mapper.MenuMapper;
import com.example.system.mapper.RoleMapper;
import com.example.system.mapper.RoleMenuMapper;
import com.example.system.others.utils.CheckUtils;
import com.example.system.service.MenuService;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class MenuServiceImpl implements MenuService {
    List<Integer> updateParentIds = new ArrayList<>();
    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private MenuMapper menuMapper;
    @Autowired
    private RoleMenuMapper roleMenuMapper;

    /**
     * 侧边栏动态获取菜单，依据用户权限
     *
     */
    @Override
    public List<Menu> getAsideMenus() {
        String account = (String) StpUtil.getLoginId();
        //获取用户的权限（既角色信息）
        Role role = roleMapper.getRoleByAccount(account);
        //角色Id
        int roleId = role.getRoleId();
        int access = role.getAccess();
        List<Menu> menus;
        //查询缓存是否存在
        String key = RedisConstant.getList_Menu_aside;
        if (redisUtils.hasKey(key + ":" + role.getAccess())) {
            //缓存存在
            menus = (List<Menu>) redisUtils.get(key + ":" + role.getAccess());
        } else {
            menus = getMenuList().stream().filter(o -> o.getAccess() >= access).collect(Collectors.toList());

            LambdaQueryWrapper<RoleMenu> lqw = new LambdaQueryWrapper<>();
            lqw.eq(RoleMenu::getRoleId, roleId);
            List<RoleMenu> rmList = roleMenuMapper.selectList(lqw);
            if (StringUtils.isNotEmpty(rmList)) {
                List<Integer> deleteList = rmList.stream().filter(o -> "DELETE".equals(o.getRelationship())).collect(Collectors.toList()).stream().map(RoleMenu::getMenuId).collect(Collectors.toList());
                if (StringUtils.isNotEmpty(deleteList)) {
                    List<Menu> deletes = menuMapper.getMenuLists(deleteList);
                    // menus和deletes的差集（menus - deletes）
                    menus = menus.stream().filter(item -> !deletes.contains(item)).collect(Collectors.toList());
                }
                List<Integer> insertList = rmList.stream().filter(o -> "INSERT".equals(o.getRelationship())).collect(Collectors.toList()).stream().map(RoleMenu::getMenuId).collect(Collectors.toList());
                if (StringUtils.isNotEmpty(insertList)) {
                    List<Menu> inserts = menuMapper.getMenuLists(insertList);
                    // menus和inserts并集（去重）
                    menus.addAll(inserts);
                    menus = menus.stream().distinct().collect(Collectors.toList());
                }
            }
            for (Menu i : menus) {
                List<Menu> list = menus.stream().filter(
                        menu -> Objects.equals(menu.getParentId(), i.getMenuId())
//                            && menu.getAccess() <= i.getAccess()
                                && (1 == menu.getType())
                ).collect(Collectors.toList());
                if (!list.isEmpty()) {
                    list.sort(Comparator.comparing(Menu::getOrderNum));
                    i.setChildren(list);
                }
            }
            menus = menus.stream().filter(o -> o.getParentId() == 0).collect(Collectors.toList());
            menus.sort(Comparator.comparing(Menu::getOrderNum));
            long time = RedisConstant.getList_Menu_aside_Time;
            redisUtils.set(key + ":" + role.getAccess(), menus, time);
        }
        return menus;
    }

    /**
     * 获取菜单列表
     */
    @Override
    public List<Menu> getList() {
        return (List<Menu>) redisUtils.get(RedisConstant.getList_Menu);
    }

    /**
     * 获取父菜单
     */
    @Override
    public List<Menu> getListMenuParent() {
        return (List<Menu>) redisUtils.get(RedisConstant.getList_MenuParent);
    }

    /**
     * 更新菜单
     */
    @Override
    public String updateMenu(MenuBo menuBo) {
        this.checkMenuBeforeUpdate(menuBo);
        updateParentIds.add(menuBo.getParentId());
        Integer parentId = menuMapper.selectById(menuBo.getMenuId()).getParentId();
        if (!parentId.equals(menuBo.getParentId()))
            updateParentIds.add(parentId);
        menuMapper.updateById(menuBo);
        this.updateMenuOrderNum();
        return "修改成功";
    }

    /**
     * 增加菜单
     */
    @Override
    @SneakyThrows
    public String insertMenu(MenuBo menu) {
        this.checkMenuBeforeInsert(menu);
        if (StringUtils.isEmpty(menu.getOrderNum()))
            menu.setOrderNum(1);
        if (StringUtils.isEmpty(menu.getParentId()))
            menu.setParentId(0);
        updateParentIds.add(menu.getParentId());
        menuMapper.insertMenu(menu);
        //更新菜单列表的排序
        this.updateMenuOrderNum();
        return "添加成功";

    }

    /**
     * 获取全部菜单
     * 采用Parent-Children模式
     */
    @Override
    public PageVo getPage(PageBo pageBo) {
        List<Menu> menus = getList();
        return new PageVo(pageBo, menus);
    }

    /**
     * 模糊查询菜单
     */
    @Override
    public R<PageVo> getPageSearchByName(MenuBo menuBo, PageBo pageBo) {
        List<Menu> menuList = menuMapper.selectList(new LambdaQueryWrapper<>());
        if (StringUtils.isNotEmpty(menuBo.getName())) {
            menuList = menuList.stream().filter(o -> o.getName().contains(menuBo.getName())).collect(Collectors.toList());
        }
        if (StringUtils.isNotEmpty(menuBo.getUrl())) {
            menuList = menuList.stream().filter(o -> o.getUrl().contains(menuBo.getUrl())).collect(Collectors.toList());
        }
        if (StringUtils.isNotEmpty(menuBo.getParentId())) {
            menuList = menuList.stream().filter(o -> o.getParentId().equals(menuBo.getParentId())).collect(Collectors.toList());
        }
        if (StringUtils.isNotEmpty(menuBo.getType())) {
            menuList = menuList.stream().filter(o -> o.getType().equals(menuBo.getType())).collect(Collectors.toList());
        }
        if (StringUtils.isEmpty(menuBo.getName()) && StringUtils.isEmpty(menuBo.getUrl()) && StringUtils.isEmpty(menuBo.getParentId()) && StringUtils.isEmpty(menuBo.getType())) {
            menuList = this.getList();
        }
        if (StringUtils.isNotEmpty(menuList)) {
            return R.ok(new PageVo(pageBo, menuList));
        }
        return R.info("没有符合条件的数据", new PageVo());
    }

    /**
     * 根据角色获取菜单列表
     */
    @Override
    public List<Integer> getListByRole(Integer roleId) {
        Role role = roleMapper.getRoleByUserId(roleId);
        int access = role.getAccess();
        List<Menu> menus = getMenuList().stream().filter(o -> o.getAccess() >= access).collect(Collectors.toList());
        List<Integer> list = menus.stream().map(Menu::getMenuId).collect(Collectors.toList());

        LambdaQueryWrapper<RoleMenu> lqw = new LambdaQueryWrapper<>();
        lqw.eq(RoleMenu::getRoleId, roleId);
        List<RoleMenu> rmList = roleMenuMapper.selectList(lqw);
        List<Integer> insertList = rmList.stream().filter(o -> "INSERT".equals(o.getRelationship())).collect(Collectors.toList()).stream().map(RoleMenu::getMenuId).collect(Collectors.toList());
        List<Integer> deleteList = rmList.stream().filter(o -> "DELETE".equals(o.getRelationship())).collect(Collectors.toList()).stream().map(RoleMenu::getMenuId).collect(Collectors.toList());


        // list和deleteList的差集（list - deleteList）
        list = list.stream().filter(item -> !deleteList.contains(item)).collect(Collectors.toList());
        // list和insertList并集（去重）
        list.addAll(insertList);
        list = list.stream().distinct().collect(Collectors.toList());


        return list;
    }

    /**
     * 获取所有菜单列表-list<Menu>格式
     */
    private List<Menu> getMenuList() {
        return menuMapper.getMenuList();
    }

    /**
     * 创建父菜单缓存
     */
    @Override
    public void createMenuParentRedis() {
        List<Menu> menus = getMenuList().stream().filter(o -> o.getParentId() == 0).collect(Collectors.toList());
        long time = RedisConstant.getList_MenuParent_Time;
        String key = RedisConstant.getList_MenuParent;
        redisUtils.set(key, menus, time);
    }

    /**
     * 创建菜单列表缓存
     */
    @Override
    public void createMenuRedis() {
        this.updateMenuId();
        List<Menu> menus = getAllMenu();
        long time = RedisConstant.getList_Menu_Time;
        String key = RedisConstant.getList_Menu;
        redisUtils.set(key, menus, time);
    }

    /**
     * 更新菜单表Id
     */
    private void updateMenuId() {
        List<Menu> menuList = getMenuList();
        List<Menu> list = menuList.stream().sorted(Comparator.comparing(Menu::getParentId).thenComparing(Menu::getOrderNum)).collect(Collectors.toList());
        for (int i = 0; i < list.size(); i++) {
            list.get(i).setNewMenuId(i + 1);
        }
        List<Menu> list1 = list;
        list = list.stream().peek(o -> {
            List<Menu> list2 = list1.stream().filter(f -> f.getMenuId().equals(o.getParentId())).collect(Collectors.toList());
            if (StringUtils.isEmpty(list2)) {
                //o没有父菜单
                o.setNewParentId(0);
            } else {
                o.setNewParentId(list2.get(0).getNewMenuId());
            }
        }).collect(Collectors.toList());
        //list为数据库应该存储的数据

        //更新sys_menu
        List<Menu> list2 = list.stream().filter(o -> !Objects.equals(o.getNewMenuId(), o.getMenuId())).collect(Collectors.toList());
        if (StringUtils.isNotEmpty(list2)) {
            menuMapper.updateMenuId_And_InsertMenuList(list);
        }
        //更新sys_role_menu
        List<Integer> roleMenu_ids = roleMenuMapper.selectList(new LambdaQueryWrapper<>()).stream().map(RoleMenu::getMenuId).collect(Collectors.toList());
        List<RoleMenu> roleMenus = list.stream().filter(o -> roleMenu_ids.contains(o.getMenuId()) && !o.getMenuId().equals(o.getNewMenuId())).map(o -> {
            RoleMenu roleMenu = new RoleMenu();
            roleMenu.setNewMenuId(o.getNewMenuId());
            roleMenu.setMenuId(o.getMenuId());
            return roleMenu;
        }).collect(Collectors.toList());
        if (StringUtils.isNotEmpty(roleMenus)) {
            roleMenuMapper.updateMenuIds(roleMenus);
        }

    }

    /**
     * 获取所有的菜单
     */
    public List<Menu> getAllMenu() {
        List<Menu> menuList = getMenuList();
        List<Menu> menus = menuList.stream().filter(o -> o.getParentId() == 0).collect(Collectors.toList());
        List<Menu> menuList1 = menuList.stream().filter(o -> o.getParentId() != 0).collect(Collectors.toList());
        menus.forEach(menu -> {
            List<Menu> list = menuList1.stream().filter(
                    o -> Objects.equals(o.getParentId(), menu.getMenuId())
            ).collect(Collectors.toList());
            if (!list.isEmpty()) {
                menu.setChildren(list);
            }
        });
        return menus;
    }

    /**
     * 删除菜单
     */
    @Override
    public String deleteMenu(MenuBo menuBo) {
        this.updateParentIds.add(menuBo.getParentId());
        menuMapper.deleteById(menuBo.getMenuId());

        //sys_role_menu
        LambdaQueryWrapper<RoleMenu> lqw = new LambdaQueryWrapper<>();
        lqw.eq(RoleMenu::getMenuId, menuBo.getMenuId());
        roleMenuMapper.delete(lqw);

        this.updateMenuOrderNum();
        return "删除成功";
    }

    /**
     * 更新角色菜单表
     */
    @Override
    public R<String> changeRoleMenu(List<Integer> newMenu, List<Integer> oldMenu, Integer roleId) {
        Role role = roleMapper.selectById(roleId);
        List<Integer> insertList = newMenu.stream().filter(item -> !oldMenu.contains(item)).collect(Collectors.toList());
        List<Integer> deleteList = oldMenu.stream().filter(item -> !newMenu.contains(item)).collect(Collectors.toList());
        if (StringUtils.isNotEmpty(insertList)) {
            List<RoleMenu> list = insertList.stream().map(i -> {
                RoleMenu roleMenu = new RoleMenu();
                roleMenu.setRoleId(roleId);
                roleMenu.setMenuId(i);
                roleMenu.setRelationship("INSERT");
                roleMenu.setAccess(role.getAccess());
                return roleMenu;
            }).collect(Collectors.toList());
            roleMenuMapper.deleteList(list);
            roleMenuMapper.insertList(list);
        }
        if (StringUtils.isNotEmpty(deleteList)) {
            List<RoleMenu> list = deleteList.stream().map(i -> {
                RoleMenu roleMenu = new RoleMenu();
                roleMenu.setRoleId(roleId);
                roleMenu.setMenuId(i);
                roleMenu.setAccess(role.getAccess());
                roleMenu.setRelationship("DELETE");
                return roleMenu;
            }).collect(Collectors.toList());
            roleMenuMapper.deleteList(list);
            roleMenuMapper.insertList(list);
        }
        checkRoleMenu();
        return R.ok("操作成功");
    }

    /**
     * 检查角色菜单表
     */
    @Async
    public void checkRoleMenu() {
        List<RoleMenu> roleMenus = roleMenuMapper.getList();
        if (StringUtils.isNotEmpty(roleMenus)) {
            List<RoleMenu> inserts = roleMenus.stream().filter(o -> "INSERT".equals(o.getRelationship())).collect(Collectors.toList());
            List<RoleMenu> deletes = roleMenus.stream().filter(o -> "DELETE".equals(o.getRelationship())).collect(Collectors.toList());
            if (StringUtils.isNotEmpty(inserts) || StringUtils.isNotEmpty(deletes)) {
                List<Menu> menuList = menuMapper.getMenuList();

                //检查权限本有可以访问菜单，却又添加了数据
                if (StringUtils.isNotEmpty(inserts) && menuList != null) {
                    List<RoleMenu> insertList = inserts.stream().filter(o -> StringUtils.isNotEmpty(
                            menuList.stream().filter(menu -> Objects.equals(o.getMenuId(), menu.getMenuId()) && menu.getAccess() >= o.getAccess()
                            ).collect(Collectors.toList())
                    )).collect(Collectors.toList());

                    //删除数组
                    if (StringUtils.isNotEmpty(insertList))
                        roleMenuMapper.deleteList(insertList);
                }

                //检查权限本就不可以访问菜单，却又添加了数据
                if (StringUtils.isNotEmpty(deletes) && menuList != null) {
                    List<RoleMenu> deleteList = inserts.stream().filter(o -> StringUtils.isNotEmpty(
                            menuList.stream().filter(menu -> Objects.equals(o.getMenuId(), menu.getMenuId()) && menu.getAccess() < o.getAccess()
                            ).collect(Collectors.toList())
                    )).collect(Collectors.toList());

                    //删除数组
                    if (StringUtils.isNotEmpty(deleteList))
                        roleMenuMapper.deleteList(deleteList);
                }
            }

        }
    }

    /**
     * 更新菜单列表的排序
     */
    @Async
    public void updateMenuOrderNum() {
        updateParentIds.forEach(parentId -> {
            List<Menu> menuList = menuMapper.getMenuListByParentId(parentId);
            List<Menu> menus = menuList.stream().sorted(Comparator.comparing(Menu::getOrderNum).thenComparing(Menu::getMenuId)).collect(Collectors.toList());
            List<Menu> menuChange = new ArrayList<>();
            for (int i = 0; i < menus.size(); i++) {
                Menu menu = menus.get(i);
                if (menu.getOrderNum() != (i + 1)) {
                    Menu menu1 = new Menu();
                    menu1.setMenuId(menu.getMenuId());
                    menu1.setOrderNum(i + 1);
                    menuChange.add(menu1);
                }
            }
            if (StringUtils.isNotEmpty(menuChange)) {
                menuMapper.updateMenuList(menuChange);
            }
        });
        updateParentIds = new ArrayList<>();
        this.updateMenuRedis();
        this.updateComponent();
    }

    /**
     * 更新Menu表的缓存
     */
    @Async
    public void updateMenuRedis() {
        Set<Object> keys = redisTemplate.keys(RedisConstant.getList_Menu + "*");
        if (keys != null) {
            for (Object key : keys)
                redisUtils.del(String.valueOf(key));
        }
        createMenuRedis();
        createMenuParentRedis();
    }

    /**
     * 更新路由
     */
    public void updateComponent() {
        List<Menu> menus = this.getAllMenu();
        List<Menu> list = new ArrayList<>();
        menus.stream().peek(menu -> {
            if ("首页".equals(menu.getName()) && "/Index".equals(menu.getUrl())) {
                String url = menu.getUrl();
                String component = "pages" + url;
                menu.setComponent(component);
                String name = url.replaceFirst("/", "");
                menu.setComName(name);
                list.add(menu);
            }
            List<Menu> children = menu.getChildren();
            if (StringUtils.isNotEmpty(children)) {
                menu.setComponent(null);
                String component = "pages" + menu.getUrl();
                children = children.stream().filter(c -> c.getType().equals(1)).peek(c -> {
                    String url = c.getUrl();
                    c.setComponent(component + url);
                    String name = url.replaceFirst("/", "");
                    c.setComName(name);
                }).collect(Collectors.toList());
                list.addAll(children);
            }
        }).collect(Collectors.toList());
        menuMapper.updateComponent(list);
    }

    /**
     * 添加菜单前，检查菜单
     */
    @SneakyThrows
    private void checkMenuBeforeInsert(Menu menu) {
        this.checkMenu(menu);
        LambdaQueryWrapper<Menu> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Menu::getUrl, menu.getUrl());
        boolean exists = menuMapper.exists(lqw);
        if (exists) {
            throw new Exception("菜单已存在,添加失败!");
        }
    }

    /**
     * 修改菜单前，检查菜单
     */
    @SneakyThrows
    private void checkMenuBeforeUpdate(Menu menu) {
        this.checkMenu(menu);
        LambdaQueryWrapper<Menu> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Menu::getUrl, menu.getUrl());
        lqw.ne(Menu::getMenuId, menu.getMenuId());
        boolean exists = menuMapper.exists(lqw);
        if (exists) {
            throw new Exception("菜单已存在,修改失败!");
        }
    }

    /**
     * 通用检查菜单
     */
    @SneakyThrows
    private void checkMenu(Menu menu) {
        if (StringUtils.isEmpty(menu.getName())) {
            throw new Exception("数据检查出现异常：菜单名称不能为空");
        }
        String url = menu.getUrl();
        if (StringUtils.isEmpty(menu.getUrl())) {
            throw new Exception("数据检查出现异常：菜单url不能为空");
        }
        if (!url.startsWith("/")) {
            throw new Exception("数据检查出现异常：菜单url格式不正确");
        }
        url = url.replaceFirst("/", "");
        if (StringUtils.isEmpty(menu.getUrl())) {
            throw new Exception("数据检查出现异常：菜单url不能为'/'");
        }
        if (CheckUtils.checkContainDigit(url)) {
            throw new Exception("数据检查出现异常：菜单url包含数字");
        }
        if (CheckUtils.checkContainSpecialChar(url)) {
            throw new Exception("数据检查出现异常：菜单url包含特殊字符");
        }
        if (CheckUtils.checkContainChineseCharacters(url)) {
            throw new Exception("数据检查出现异常：菜单url包含汉字");
        }
        if (StringUtils.isEmpty(menu.getType())) {
            throw new Exception("数据检查出现异常：菜单类型不能为空");
        }
        if (StringUtils.isEmpty(menu.getAccess())) {
            throw new Exception("数据检查出现异常：访问权限不能为空");
        }
        if (StringUtils.isEmpty(menu.getIcon())) {
            throw new Exception("数据检查出现异常：菜单图标不能为空");
        }
        if (StringUtils.isEmpty(menu.getAccess())) {
            throw new Exception("数据检查出现异常：菜单权限不能为空");
        }
    }
}
